from ui_mainwindow import Ui_MainWindow
from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QProgressDialog
from PyQt5 import QtCore
from cipher import Cipher


class MainWindow(QWidget, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        # 密码器，负责文件的加密与解密
        self.cipher = None
        # 进度条对话框，在长时间加解密时实例化并显示
        self.pd = None
        # 将要加密的文件路径
        self.__infile = self.lab_infile.text()
        # 加密后文件的输出目录
        self.__outdir = self.lab_outdir.text()
        self.setWindowTitle("file encryption")
        # 程序支持的四种加密算法
        self.cb_alg.addItems(["AES-256", "AES-192", "AES-128", "DES"])

    def check_psw(self, psw):
        """
            这个方法根据当前combobox选择的算法，得到正确的密码
            当用户输入的密码不满足当前算法的密码条件（密码位数
        太多或者为空）方法将返回空字符串
            当用户输入的密码位数太少，方法将调用str.center方法
        添加‘0’并将原来输入的密码居中
        :param psw: 当前用户输入的密码
        :return: 经过处理后能够被相应加密算法使用的密码
        """
        alg = self.cb_alg.currentText()
        if not psw.__len__():
            return ""
        if alg == "AES-256":
            if psw.__len__() > 32:
                return ""
            psw = psw.center(32, "0")
        elif alg == "AES-192":
            if psw.__len__() > 24:
                return ""
            psw = psw.center(24, "0")
        elif alg == "AES-128":
            if psw.__len__() > 16:
                return ""
            psw = psw.center(16, "0")
        else:
            if psw.__len__() > 8:
                return ""
            psw = psw.center(8, "0")
        return psw

    def on_cipher_start(self, op, c):
        """
        为Cipher对象中cipher_start信号的对应槽函数
        :param op: str类型，当前Cipher执行加密操作或是解密操作
        :param c: int类型，当前待加密文件中size_block的总数量
        :return:
        """
        # 当前文件中size_block数量小于10000时，不显示进度框
        if c < 10000:
            self.pd = None
            return
        # 根据op的值，显示加密或者解密进度框
        self.pd = QProgressDialog("encrypt...", None, 0, c, self) if op == "encrypt" \
            else QProgressDialog("decrypt...", None, 0, c, self)
        # 设置进度框为模态，届时将自动调用QCoreApplication.processEvent防止主线程阻塞
        self.pd.setWindowModality(QtCore.Qt.WindowModal)

    def on_cipher_variety(self, op, i):
        """
        为Cipher对象中cipher_variety信号的对应槽函数
        :param op: str类型，当前Cipher执行加密操作或是解密操作
        :param i:  int类型，当前已加（解）密的size_block的数量
        :return:
        """
        if self.pd:
            # 更新进度条的执行进度同时调用QCoreApplication.processEvent处理事件
            self.pd.setValue(i)
        if i == -1:
            if self.pd:
                self.pd.setVisible(False)
            self.pd = None
            self.cipher = None
            QMessageBox.information(self, "info", "encrypt ok!", QMessageBox.Yes) if op == "encrypt" \
                else QMessageBox.information(self, "info", "decrypt ok!", QMessageBox.Yes)

    @QtCore.pyqtSlot()
    def on_btn_open_clicked(self):
        """
        按钮“open”的槽函数
        主要负责打开文件对话框，得到待加密文件的文件绝对路径
        :return:
        """
        self.__infile, __filter = QFileDialog.getOpenFileName(self, "openfile", '.', "All files(*.*)")
        if self.__infile.strip():
            self.lab_infile.setText(self.__infile)

    @QtCore.pyqtSlot()
    def on_btn_outdir_clicked(self):
        """
        按钮“outdir”的槽函数
        主要负责打开目录对话框，得到加密后文件存放目录的绝对路径
        :return:
        """
        self.__outdir = QFileDialog.getExistingDirectory(self, "opendir", '.')
        if self.__outdir.strip():
            self.lab_outdir.setText(self.__outdir)

    @QtCore.pyqtSlot()
    def on_btn_encrypt_clicked(self):
        """
        按钮“encrypt”的槽函数，负责根据相应
        的加密算法加密用户所选择的待加密文件
        :return:
        """
        # 通过check_psw方法，将从edt_psw获得的密码转换成相应加（解）密算法能够使用的密码
        psw = self.check_psw(self.edt_psw.text())
        # 当密码过长或为空时，弹出错误提示
        if not psw:
            QMessageBox.critical(self, "error", "password is empty or too long", QMessageBox.Yes)
            return
        # 当待加密文件路径或输出目录为空时，弹出错误提示
        if not self.__infile or not self.__outdir:
            QMessageBox.critical(self, "error", "infile or outdir not select", QMessageBox.Yes)
            return
        # 程序将加密后的文件增加‘.moe’后缀以代表此文件已被加密
        # 故当文件后缀名为‘.moe’时弹出错误调试，表示该文件为一个加密后的文件
        if self.__infile.endswith(".moe"):
            QMessageBox.critical(self, "error", "infile need normal file", QMessageBox.Yes)
            return
        # 实例化密码器，并为密码器对象的cipher_start、cipher_variety信号关联对应槽函数
        self.cipher = Cipher(alg=self.cb_alg.currentText(), psw=psw, infile=self.__infile,
                        outdir=self.__outdir)
        self.cipher.cipher_start.connect(self.on_cipher_start)
        self.cipher.cipher_variety.connect(self.on_cipher_variety)
        # 调用密码器的加密方法，开始加密
        self.cipher.encrypt()

    @QtCore.pyqtSlot()
    def on_btn_decrypt_clicked(self):
        """
        按钮“decrypt”的槽函数，负责根据相应
        的加密算法解密用户所选择的‘.moe’后缀名文件
        :return:
        """
        psw = self.check_psw(self.edt_psw.text())
        if not psw:
            QMessageBox.critical(self, "error", "password is empty or too long", QMessageBox.Yes)
            return
        if not self.__infile or not self.__outdir:
            QMessageBox.critical(self, "error", "infile or outdir not select", QMessageBox.Yes)
            return
        # 当文件后缀名不是‘.moe’时，弹出错误提示
        if not self.__infile.endswith(".moe"):
            QMessageBox.critical(self, "error", "infile need '.moe' file", QMessageBox.Yes)
            return
        # 实例化密码器，并为密码器对象的cipher_start、cipher_variety信号关联对应槽函数
        self.cipher = Cipher(alg=self.cb_alg.currentText(), psw=psw, infile=self.__infile,
                        outdir=self.__outdir)
        self.cipher.cipher_start.connect(self.on_cipher_start)
        self.cipher.cipher_variety.connect(self.on_cipher_variety)
        # 调用密码器的解密方法，开始解密
        self.cipher.decrypt()

    @QtCore.pyqtSlot()
    def on_btn_cls_infile_clicked(self):
        """
        槽函数，负责清除待加密文件路径
        :return:
        """
        self.__infile = ""
        self.lab_infile.setText(self.__infile)

    @QtCore.pyqtSlot()
    def on_btn_cls_outdir_clicked(self):
        """
        槽函数，负责清除加密文件的输出目录
        :return:
        """
        self.__outdir = ""
        self.lab_outdir.setText(self.__outdir)

